import {ElLoading} from 'element-plus';
import SparkMD5 from "spark-md5";
import {generateDownloadUuid, message} from "@/util/util";
import {getRequest, postFileRequest, postRequest} from "@/util/api";

function md5(file, chunkSize) {
    return new Promise((resolve, reject) => {
        let chunks = Math.ceil(file.size / chunkSize);
        let currentChunk = 0;
        let spark = new SparkMD5.ArrayBuffer();
        let fileReader = new FileReader();
        fileReader.onload = function (e) {
            spark.append(e.target.result);
            currentChunk++;
            if (currentChunk < chunks) {
                loadNext();
            } else {
                let md5 = spark.end();
                resolve(md5);
            }
        };

        fileReader.onerror = function (e) {
            reject(e);
        };

        function loadNext() {
            let start = currentChunk * chunkSize;
            let end = start + chunkSize;
            if (end > file.size) {
                end = file.size;
            }
            fileReader.readAsArrayBuffer(file.slice(start, end));
        }

        loadNext();
    });
}

const calculateMD5 = async (file) => {
    return await md5(file, EACH_FRAGMENT_SIZE);
}

function loading(text) {
    return ElLoading.service({
        lock: true,
        text: text,
        background: 'rgba(0, 0, 0, 0.7)',
    });
}

const EACH_FRAGMENT_SIZE = 1024 * 1024 * 2;
const MAX_FILE_SIZE = 1024 * 1024 * 200;

export async function showFilePicker() {
    let fileHandle;
    try {
        const pickerOpts = {
            excludeAcceptAllOption: false,
            multiple: false,
        };
        fileHandle = await window.showOpenFilePicker(pickerOpts);
    } catch (e) {
        if (e.name === 'AbortError' && e.message === 'The user aborted a request.') {
            return;
        } else {
            throw e;
        }
    }

    const file = await fileHandle[0].getFile();
    if (file.size > MAX_FILE_SIZE) {
        message("上传文件过大,最多不超过200MB", "warning");
        return;
    }
    return {
        name: file.name,
        percentage: 0,
        file: file,
        totalSize: file.size,
        totalCompleteSize: 0,
        pause: false,
        fragmentList: [],
    };
}

export async function checkFileMd5(uploadFile) {
    const checkFileMd5Tip = loading("正在校验文件" + uploadFile.name + "的MD5,请稍候");
    const fileMd5 = await calculateMD5(uploadFile.file);
    uploadFile.fileMd5 = fileMd5;
    checkFileMd5Tip.close();

    await getRequest("/file-info/checkMd5", {
        fileMd5
    }, () => {
        uploadFile.percentage = 100;
        uploadFile.totalCompleteSize = uploadFile.file.size;
    }, async () => {
        await sliceFile(uploadFile);
    });
}

async function sliceFile(uploadFile) {
    const fragmentCount = Math.floor(uploadFile.file.size / EACH_FRAGMENT_SIZE) + 1;
    for (let i = 0; i < fragmentCount; i++) {
        uploadFile.fragmentList.push({
            id: i,
            fragmentFile: uploadFile.file.slice(i * EACH_FRAGMENT_SIZE, (i + 1) * EACH_FRAGMENT_SIZE),
            completeSize: 0,
            name: uploadFile.name + "--分片" + (i + 1),
            percentage: 0,
            fileMd5: uploadFile.fileMd5,
        });
    }
}

export async function checkFragmentMd5(uploadFile, fragmentIndex, dirId, resolve) {
    if (fragmentIndex === uploadFile.fragmentList.length) {
        await generateFile(uploadFile, dirId, resolve);
        return;
    }
    if (uploadFile.pause) {
        return;
    }
    if (uploadFile.fragmentList[fragmentIndex].percentage === 100) {
        await checkFragmentMd5(uploadFile, fragmentIndex + 1, dirId, resolve);
        return;
    }

    const fragmentMd5 = await calculateMD5(uploadFile.fragmentList[fragmentIndex].fragmentFile);
    uploadFile.fragmentList[fragmentIndex].fragmentMd5 = fragmentMd5;

    await getRequest("/fragment/checkMd5", {
        fragmentMd5
    }, () => {
        uploadFile.fragmentList[fragmentIndex].percentage = 100;
        uploadFile.fragmentList[fragmentIndex].completeSize = uploadFile.fragmentList[fragmentIndex].fragmentFile.size;
    }, async () => {
        await uploadFragment(uploadFile, fragmentIndex, dirId, resolve);
    });
}

async function generateFile(uploadFile, dirId, resolve) {
    await postRequest("/file-info/generateFile", null, {
        dirId: dirId,
        fileName: uploadFile.name,
        fileMd5: uploadFile.fileMd5,
        fileSize: uploadFile.totalSize,
    }, () => {
        resolve(uploadFile);
    });
}

async function uploadFragment(uploadFile, fragmentIndex, dirId, resolve) {
    const uploadFragmentFile = uploadFile.fragmentList[fragmentIndex];
    const onUploadProgress = async (progressEvent) => {
        uploadFragmentFile.percentage = parseInt(Number((progressEvent.loaded / progressEvent.total * 100)).toFixed(0));
        uploadFragmentFile.completeSize = progressEvent.loaded / progressEvent.total * uploadFragmentFile.fragmentFile.size;
        if (uploadFragmentFile.percentage === 100) {
            await checkFragmentMd5(uploadFile, fragmentIndex + 1, dirId, resolve);
        }
        await updateTotalPercentage(uploadFile);
    };

    const formData = new FormData();
    formData.append("file", uploadFragmentFile.fragmentFile, uploadFragmentFile.name);

    await generateDownloadUuid(async (uuid) => {
        await postFileRequest("/fragment/upload", {
            fragmentMd5: uploadFragmentFile.fragmentMd5,
            fileMd5: uploadFragmentFile.fileMd5,
            fragmentSize: uploadFragmentFile.fragmentFile.size,
            uuid: uuid
        }, formData, onUploadProgress);
    });
}

async function updateTotalPercentage(uploadFile) {
    let totalCompleteSize = 0;
    for (let i = 0; i < uploadFile.fragmentList.length; i++) {
        totalCompleteSize += uploadFile.fragmentList[i].completeSize;
    }
    uploadFile.totalCompleteSize = totalCompleteSize;
    uploadFile.percentage = parseInt(Number(uploadFile.totalCompleteSize / uploadFile.totalSize * 100).toFixed(0));
}
